﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OxyPlot;

namespace AutoCorrelatorGUI.Model
{
   public class Well_Comparison
   {
      public double OutputCorrelation { get; set; }
      public double OutputRMSTotal { get; set; }
      public double OutputArea { get; set; }
      public double OutputMaxDiff { get; set; }

      public double FFTCorrelation { get; set; }
      public double FFTRMSTotal { get; set; }
      public double FFTArea { get; set; }
      public double FFTMaxDiff { get; set; }

      public double PowerCorrelation { get; set; }
      public double PowerRMSTotal { get; set; }
      public double PowerArea { get; set; }
      public double PowerMaxDiff { get; set; }

      public double WaveletCorrelation { get; set; }
      public double WaveletRMSTotal { get; set; }
      public double WaveletArea { get; set; }
      public double WaveletMaxDiff { get; set; }

      private double CalcCorr(Dictionary<int, double> x, Dictionary<int, double> y)
      {
         //Since the different number of items can mess up the calculation of the mean and std deviation. Need to do this the hard way
         double sumX = 0.0;
         double sumY = 0.0;
         double sumXY = 0.0;
         double sumX2 = 0.0;
         double sumY2 = 0.0;

         double n = 0.0;

         foreach (var xPair in x)
         {
            if (!y.ContainsKey(xPair.Key))
               continue;

            double xVal = xPair.Value;
            double yVal = y[xPair.Key];

            sumX += xVal;
            sumY += yVal;
            sumXY += xVal * yVal;
            sumX2 += xVal * xVal;
            sumY2 += yVal * yVal;

            n += 1.0;
         }

         double num = n * sumXY - (sumX * sumY);
         double den = Math.Sqrt(n * sumX2 - sumX * sumX) * Math.Sqrt(n * sumY2 - sumY * sumY);

         return num / den;
      }

      private double CalcCorr(double[] x, double[] y)
      {
         //Since the different number of items can mess up the calculation of the mean and std deviation. Need to do this the hard way
         double sumX = 0.0;
         double sumY = 0.0;
         double sumXY = 0.0;
         double sumX2 = 0.0;
         double sumY2 = 0.0;

         double n = 0.0;

         for (int i = 0; i < x.Length; i++)
         {
            double xVal = x[i];
            double yVal = y[i];

            sumX += xVal;
            sumY += yVal;
            sumXY += xVal * yVal;
            sumX2 += xVal * xVal;
            sumY2 += yVal * yVal;

            n += 1.0;
         }

         double num = n * sumXY - (sumX * sumY);
         double den = Math.Sqrt(n * sumX2 - sumX * sumX) * Math.Sqrt(n * sumY2 - sumY * sumY);

         return num / den;
      }

      private void BuildArrays(Dictionary<int, double> x, Dictionary<int, double> y, ref double[] xArr, ref double[] yArr)
      {
         int minX = Math.Min(x.Keys.Min(), y.Keys.Min());
         int maxX = Math.Max(x.Keys.Max(), y.Keys.Max());

         xArr = new double[maxX - minX + 1];
         yArr = new double[maxX - minX + 1];

         int count = 0;

         for (int i = minX; i <= maxX; i++)
         {
            if (x.ContainsKey(i))
               xArr[count] = x[i];

            if (y.ContainsKey(i))
               yArr[count] = y[i];

            count++;
         }
      }

      private void BuildArrays(List<DataPoint> x, List<DataPoint> y, ref double[] xArr, ref double[] yArr)
      {
         Debug.Assert(x.Count == y.Count, "X and Y must have the same number of elements");

         xArr = new double[x.Count];
         yArr = new double[x.Count];

         int count = 0;

         for (int i = 0; i < x.Count; i++)
         {
            xArr[i] = x[i].Y;
            yArr[i] = y[i].Y;

            count++;
         }
      }

      private void CalcComparison(Dictionary<int, double> x, Dictionary<int, double> y, ref double correlation, ref double rms, ref double area, ref double maxDiff)
      {
         double[] xArr = null;
         double[] yArr = null;

         BuildArrays(x, y, ref xArr, ref yArr);

         CalcComparison(xArr, yArr, ref correlation, ref rms, ref area, ref maxDiff);
      }

      private void CalcComparison(List<DataPoint> x, List<DataPoint> y, ref double correlation, ref double rms, ref double area, ref double maxDiff)
      {
         double[] xArr = null;
         double[] yArr = null;

         BuildArrays(x, y, ref xArr, ref yArr);

         CalcComparison(xArr, yArr, ref correlation, ref rms, ref area, ref maxDiff);
      }

      private void CalcComparison(double[] x, double[] y, ref double correlation, ref double rms, ref double area, ref double maxDiff)
      {
         correlation = 0.0;
         rms = 0.0;
         area = 0.0;
         maxDiff = 0.0;

         double prevDiff = 0.0;
         double spacing = Properties.Settings.Default.IntervalTimeMS;

         for (int i = 0; i < x.Length; i++)
         {
            double diff = y[i] - x[i];

            maxDiff = Math.Max(maxDiff, Math.Abs(diff));

            rms += (diff * diff);

            //Calc the addition to the area
            if (prevDiff * diff <= 0.0 && (prevDiff - diff) != 0) //Make sure we arent parallel also
            {
               //There must be an intersection for this to happen. Calc intersection point
               double intPoint = prevDiff / (prevDiff - diff);

               //Break up into 2 smaller areas
               area += (Math.Abs(prevDiff) * spacing * intPoint) / 2.0;
               area += (Math.Abs(diff) * spacing * (1.0 - intPoint)) / 2.0;
            }
            else
            {
               area += (Math.Abs(prevDiff) * spacing) / 2.0;
               area += (Math.Abs(diff) * spacing) / 2.0;
            }

            prevDiff = diff;
         }

         //Use the CalcCorr method to determine the correlation
         correlation = CalcCorr(x, y);

         rms = Math.Sqrt(rms / (double)y.Length);
      }

      public void Calc(Well thisWell, Well compare)
      {
         //Zero Values
         OutputCorrelation = 0.0;
         OutputRMSTotal = 0.0;
         OutputArea = 0.0;
         OutputMaxDiff = 0.0;
         FFTCorrelation = 0.0;
         FFTRMSTotal = 0.0;
         FFTArea = 0.0;
         FFTMaxDiff = 0.0;
         PowerCorrelation = 0.0;
         PowerRMSTotal = 0.0;
         PowerArea = 0.0;
         PowerMaxDiff = 0.0;
         WaveletCorrelation = 0.0;
         WaveletRMSTotal = 0.0;
         WaveletArea = 0.0;
         WaveletMaxDiff = 0.0;

         if (compare == null)
            return;

         double corr = 0.0;
         double rms = 0.0;
         double area = 0.0;
         double maxDiff = 0.0;

         //Calculate output values first
         CalcComparison(thisWell.Output.DataStore, compare.Output.DataStore, ref corr, ref rms, ref area, ref maxDiff);

         OutputCorrelation = corr;
         OutputRMSTotal = rms;
         OutputArea = area;
         OutputMaxDiff = maxDiff;

         //Calculate the diff in FFT Values
         CalcComparison(thisWell.Transform.FourierData, compare.Transform.FourierData, ref corr, ref rms, ref area, ref maxDiff);

         FFTCorrelation = corr;
         FFTRMSTotal = rms;
         FFTArea = area;
         FFTMaxDiff = maxDiff;

         //Calculate the diff in FFT Values
         CalcComparison(thisWell.Transform.PowerData, compare.Transform.PowerData, ref corr, ref rms, ref area, ref maxDiff);

         PowerCorrelation = corr;
         PowerRMSTotal = rms;
         PowerArea = area;
         PowerMaxDiff = maxDiff;

         //Calculate the diff in Wavelet Values
         CalcComparison(thisWell.Transform.WaveletData, compare.Transform.WaveletData, ref corr, ref rms, ref area, ref maxDiff);

         WaveletCorrelation = corr;
         WaveletRMSTotal = rms;
         WaveletArea = area;
         WaveletMaxDiff = maxDiff;

      }
   }
}
